using System;
using System.Text;
using System.Data;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Web;

using Framework;

namespace SchemaMonitor
{
    //Collection Class (Customisable half)
    public partial class CTypeList
    {
        #region Filters
        public CTypeList Manual { get { return GetByDiscoveryId(EDiscovery.Manual); } }
        public CTypeList Auto   { get { return GetByDiscoveryId(EDiscovery.Auto); } }
        public CTypeList System { get { return GetByDiscoveryId(EDiscovery.System); } }
        public CTypeList GetByDiscoveryId(EDiscovery d) { return GetByDiscoveryId((byte)d); }
        #endregion

        #region Aggregation
        #endregion

        #region Searching (Optional)
        //Represents a simple search box to search PK and any string columns (add overloads as required, based on the pattern below)
        //e.g. public CTypeList Search(string nameOrId, int schemaId, byte discoveryId) { ...
        public CTypeList Search(string nameOrId)
        {
            //1. Normalisation
            nameOrId = (nameOrId??string.Empty).Trim().ToLower();
            
            //2. Start with a complete list
            CTypeList results = this;
            
            //3. Use any available index, such as those generated for fk/bool columns
            //Normal Case - non-unique index (e.g. foreign key)
            //if (int.MinValue != schemaId) results = results.GetBySchemaId(schemaId);
            //if (byte.MinValue != discoveryId) results = results.GetByDiscoveryId(discoveryId);

            //Special case - unique index (e.g. primary key)
            /*
            if (!string.IsNullOrEmpty(nameOrId)) 
            {
                int id;
                if (int.TryParse(nameOrId, out id))
                {
                    CType obj = this.GetById(id);
                    if (null != obj)
                    {
                        results = new CTypeList(1);
                        results.Add(obj);
                        return results;
                    }
                }
            }
            */
            
            //4. Exit early if remaining (non-index) filters are blank
            if (string.IsNullOrEmpty(nameOrId)) return results; 
            
            //5. Manually search each record using custom match logic, building a shortlist
            CTypeList shortList = new CTypeList();
            foreach (CType i in results)
                if (Match(nameOrId, i))
                    shortList.Add(i); 
            return shortList;
        }
        //Manual Searching e.g for string-based columns i.e. anything not indexed (add more params if required)
        private bool Match(string name, CType obj)
        {
            if (!string.IsNullOrEmpty(name)) //Match any string column
            {
                if (null != obj.TypeName && obj.TypeName.ToLower().Contains(name))   return true;
                if (null != obj.TypeApiPath && obj.TypeApiPath.ToLower().Contains(name))   return true;
                return false;   //If filter is active, reject any items that dont match
            }
            return true;    //No active filters (should catch this in step #4)
        }
        #endregion

        #region Cloning
        public CTypeList Clone(CDataSrc target) //, int parentId)
        {
            //No Transaction
            if (target is CDataSrcRemote)
                return Clone(target, null); //, parentId);

            //Transaction
            using (IDbConnection cn = target.Local.Connection())
            {
                IDbTransaction tx = cn.BeginTransaction();
                try
                {
                    CTypeList clone = Clone(target, tx); //, parentId);
                    tx.Commit();
                    return clone;
                }
                catch
                {
                    tx.Rollback();
                    throw;
                }
            }
        }
        public CTypeList Clone(CDataSrc target, IDbTransaction txOrNull) //, int parentId)
        {
            CTypeList list = new CTypeList(this.Count);
            foreach (CType i in this)
                list.Add(i.Clone(target, txOrNull)); //, parentId));  *Child entities must reference the new parent
            return list;
        }
        #endregion
        
        #region Export to Csv
        //Web - Need to add a project reference to System.Web, or comment out these two methods
        public void ExportToCsv(HttpResponse response) { ExportToCsv(response, "Types.csv"); }
        public void ExportToCsv(HttpResponse response, string fileName)
        {
            CDataSrc.ExportToCsv(response, fileName); //Standard response headers
            StreamWriter sw = new StreamWriter(response.OutputStream);
            ExportToCsv(sw);
            sw.Flush();
            response.End();
        }

        //Non-web
        public void ExportToCsv(string filePath)
        {
            StreamWriter sw = new StreamWriter(filePath);
            ExportToCsv(sw);
            sw.Close();
        }

        //Logic
        protected void ExportToCsv(StreamWriter sw)
        {
            string[] headings = new string[] {"TypeId", "TypeSchemaId", "TypeDiscoveryId", "TypeName", "TypeApiPath", "TypeCreated"};
            CDataSrc.ExportToCsv(headings, sw);
            foreach (CType i in this)
            {
                object[] data = new object[] {i.TypeId, i.TypeSchemaId, i.TypeDiscoveryId, i.TypeName, i.TypeApiPath, i.TypeCreated};
                CDataSrc.ExportToCsv(data, sw);
            }
        }
        #endregion

        #region GetOrCreate
        public CType GetOrCreate_Metadata(json.CMetadata meta, object value, CUser c)
        {
            //2 signatures: meta/name
            var sigMeta = new CSignature(meta);
            var sigName = new CSignature(meta.Type);
            CType t = null;

            //Return existing by meta hash
            var match = CSignature.Cache.GetByHash(sigMeta);
            if (match.Count > 0)
            {
                t = match[0].Type;
                if (! t.HasMeta)
                {
                    t.Metadata = meta;
                    t.Save();
                }

                return AddSignatures(t, sigName, sigMeta);
            }

            //Return existing by name hash
            match = CSignature.Cache.GetByHash(sigName);
            if (match.Count > 0)
            {
                t = match[0].Type;
                t.Metadata = meta; //changed
                t.Save();
                return AddSignatures(t, sigName, sigMeta);
            }

            //Create type (but not tags)
            t = new CType();
            t.DiscoveryId = EDiscovery.Auto;
            t.Metadata = meta;
            t.Save();

            t = AddSignatures(t, sigName, sigMeta);

            //Add Tags
//            foreach (var i in meta.Fields)
//                t.GetOrCreateTag(i, value, c);

            return t;
        }

        public CType GetOrCreate_Manual(string name, string apiPath)
        {
            return GetOrCreate_Manual(name, apiPath, CSchema.DefaultId);
        }
        public CType GetOrCreate_Manual(string name, string apiPath, int schemaId)
        {
            //Signatures
            var sigN = new CSignature(name, true);
            var sigP = CSignature.GetSigManual(name, apiPath);
            //var sigPath = new CSignature(apiPath, true);

            //#1. Match on Name (alone)
            var matchN = CSignature.Cache.Manual.GetByHash(sigN.SigHash);
            if (matchN.Count == 1)
                return AddSignatures(matchN[0].Type, sigN, sigP);

            //Match on Path (insufficient)
            var matchP = CSignature.Cache.Manual.GetByHash(sigP.SigHash);
            if (matchP.Count == 1)
                return AddSignatures(matchP[0].Type, sigN, sigP);
  
            //3.Name+Path, but sigs missing (slow-search)
            foreach (var i in CType.Cache.Manual)
                if (i.TypeName == name && i.TypeApiPath == apiPath)
                    return AddSignatures(i, sigN, sigP);

            //Create new type
            var t = new CType();
            t.TypeSchemaId = schemaId;
            t.DiscoveryId = EDiscovery.Manual;
            t.TypeApiPath = apiPath;
            t.TypeName = name;
            t.TypeHasId = true; //Manual ones have ids
            t.Save();

            //Add the signatures
            AddSignatures(t, sigN, sigP);
            return t;
        }

        public static CType AddSignatures(CType type, params CSignature[] sigs)
        {
            foreach (var i in sigs)
            {
                if (string.IsNullOrEmpty(i.SigDescription))
                    continue;

                var match = type.Signatures.GetByHash(i.SigHash);
                if (0 == match.Count)
                {
                    i.SigTypeId = type.TypeId;
                    i.Save();
                }
            }
            return type;
        }


        public const string FB_LONG = "numeric string";
        public const string FB_STRING = "string";
        public const string FB_INT = "int";
        public const string FB_UINT = "unsigned int32";
        public const string FB_BOOL = "bool";
        public const string FB_DATE = "datetime";
        public const string FB_SINGLE = "float (min: -24) (max: 24)";

        [Obsolete]
        public CType GetOrCreate_System(string metaTypeName)
        {
            var t = metaTypeName;
            var sig = new CSignature(t);

            //Find existing (by fb-type-name)
            var matching = CSignature.Cache.Auto.GetByHash(sig);
            if (matching.Count == 1)
                return matching[0].Type;

            //Map to a local type
            Type tt = null;
            switch (t)
            {
                case FB_LONG :
                    tt = typeof(long);
                    break;

                case FB_STRING:
                    tt = typeof(string);
                    break;

                case FB_BOOL:
                    tt = typeof(bool);
                    break;

                case FB_INT:
                case FB_UINT:
                    tt = typeof(int);
                    break;

                case FB_SINGLE:
                    tt = typeof(Single);
                    break;

                case FB_DATE:
                    tt = typeof(DateTime);
                    break;
            }
 

            //Create a non-system type
            if (null == tt)
            {
                var unknown = new CType();
                unknown.TypeName = t;
                unknown.DiscoveryId = EDiscovery.Auto;
                unknown.TypeHasId = metaTypeName.Contains("|");
                unknown.Save();

                sig.SigTypeId = unknown.TypeId;
                sig.Save();

                if (unknown.TypeHasId)
                {
                    var m = new json.CMetadata(new json.CDict(), null, null);
                    m.Type = metaTypeName;
                    m.Fields = new List<json.CMetaField>();
                    foreach (var i in metaTypeName.Split(("|").ToCharArray()))
                    {
                        var s = new CSignature(i);
                        var ss = CSignature.Cache.GetByHash(s.SigHash);
                        if (ss.Count == 1)
                        {
                            var x = ss[0].Type;
                            if (x.HasMeta)
                                m.Fields.AddRange(x.Metadata.Fields);
                        }
                    }
                    if (m.Fields.Count > 0)
                    {
                        unknown.Metadata = m;
                        unknown.Save();
                    }
                }

                return unknown;
            }

            //Create/Return System Type (with extra sig)
            CType ttt = GetOrCreate_System(tt);
            return AddSignatures(ttt, sig);
        }

        public CType GetOrCreate_System(Type type) { return GetOrCreate_System(type, CSchema.DefaultId); }
        public CType GetOrCreate_System(Type type, int schemaId)
        {
            //System Typename signature
            var s1 = new CSignature(type);

            //Match on Hash (System-Types only)
            var match = CSignature.Cache.System.GetByHash(s1.SigHash);
            if (match.Count >= 1)
                return match[0].Type;

            //Create new type
            var t = new CType();
            t.DiscoveryId  = s1.DiscoveryId;
            t.TypeName     = s1.Name.Replace("System.", string.Empty);
            t.TypeSchemaId = schemaId;
            t.Save();

            //Store the signature
            return AddSignatures(t, s1);
        }

        public CType GetOrCreate_Auto(object value, string path, CUser c, out bool isList)
        {

            //Hashing (Keys+Path)
            var sigNormal = CSignature.GetSigAuto(path, value);
            isList = sigNormal.IsList;

            //#1. System type (no keys, not dict)
            if (sigNormal.IsSystem)
                return GetOrCreate_System(value.GetType());


            //Match any existing type on keys/path
            var matchNormal = CSignature.Cache.Auto.GetByHash(sigNormal.SigHash);
            if (matchNormal.Count == 1)
                return matchNormal[0].Type;

            //Keys only, new path (match for now)
            var sigNoPath = CSignature.GetSigAuto(string.Empty, value);
            var matchNoPath = CSignature.Cache.Auto.GetByHash(sigNoPath.SigHash);
            if (matchNoPath.Count == 1 && sigNoPath.Name.Length > 50)
            {
                var t = matchNoPath[0].Type;
                AddSignatures(t, sigNormal, sigNoPath);
                return t;
            }


            //List Case
            isList = sigNormal.IsList;
            if (isList)
            {
                var arr = (object[])value;
                if (arr.Length > 0)
                {
                    object first = arr[0];
                    sigNormal = CSignature.GetSigArray(path, arr);

                    var type = GetOrCreate_Auto(first, path, c, out isList);
                    isList = true;
                    return type;
                }
                return GetOrCreate_System(new object().GetType());
            }


            //Composite case - separate function
            if (sigNormal.IsDict)
            {
                isList = false;
                var d = (Dictionary<string, object>)value;
                return GetOrCreate(path, d, c);
            }

            //System type *** exception??
            return GetOrCreate_System(value.GetType());
        }
		public CType GetBySig(string signature)
		{
			var s = new CSignature(signature);
			var ss = CSignature.Cache.GetByHash(s);
			if (ss.Count == 0) return null;
			return ss[0].Type;
		}
		public CType GetOrCreate(string tagPath, Dictionary<string, object> value, CUser c)
        {
            if (CSchema.DefaultId == CSchema.FB.SchemaId)
            {
                var id = json.CNodeRoot.GetIdAsString(value);
                if (!string.IsNullOrEmpty(id))
                {
                    try
                    {
                        var meta = CEngine.DownloadMeta(id, c);
                        if (null != meta)
                            return GetOrCreate_Metadata(meta, value, c);
                    }
                    catch { }
                }
            }

            //Create this type using keys etc
            var keys = new List<string>(value.Keys);
            return GetOrCreate(tagPath, keys, c);
        }
        private CType GetOrCreate(string tagPath, List<string> keys, CUser c)
        {

            //Identify by set of tagnames (exact match)
            var sig = CSignature.GetSigAuto(tagPath, keys);
            var match = CSignature.Cache.GetByHash(sig);
            if (match.Count > 0)
                return match[0].Type;

			/*
            //Also accept partial match, if these keys all match one, and only one
            var partial = new CTypeList(1);
            foreach (var i in this)
                if (i.Tags.FoundAll(keys))
                    partial.Add(i);
            if (1 == partial.Count)
            {
                var m = partial[0];
                sig.SigTypeId = m.TypeId;
                sig.Save();
                return m;
            }
			*/

            //Create new composite type
            var newType = new CType();
            newType.DiscoveryId = EDiscovery.Auto;
            newType.TypeHasId = keys.Contains("id") && keys.Count > 2;
			newType.TypeName = tagPath + " :: " + CUtilities.ListToString(keys);
			newType.Save();


            //Store the signature
            sig.SigTypeId = newType.TypeId;
            sig.Save();

            return newType;
        }
        #endregion


        public new void Add(CType item)
        {
            if (null != _index && !_index.ContainsKey(item.TypeId))
                _index[item.TypeId] = item;

            //_indexByApiPath = null;
            _indexByDiscoveryId = null;
            _indexByHasId = null;
            //_indexByName = null;
            _indexBySchemaId = null;

            base.Add(item);
        }
    }
}